using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

using LensflareGameFramework;
using Util;
using Camera;
using FarseerPhysics.Dynamics;
using FarseerPhysics;
using FarseerPhysics.Collision.Shapes;
using System.Diagnostics;
using System.Reflection;

namespace LensflareAIContest {
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class LensflareAIContestGame : Microsoft.Xna.Framework.Game {
        AI[] players = new AI[] {
            //(AI)Activator.CreateInstance(Assembly.LoadFrom("AILor.dll").GetType("LensflareAIContest.AILor")),
			//(AI)Activator.CreateInstance(Assembly.LoadFrom("AILor.dll").GetType("LensflareAIContest.AIDummy")),

            //(AI)Activator.CreateInstance(Assembly.LoadFrom("AIWil.dll").GetType("LensflareAIContest.AIWil")),
            //(AI)Activator.CreateInstance(Assembly.LoadFrom("AIWil.dll").GetType("LensflareAIContest.AIWil2")),

            (AI)Activator.CreateInstance(Assembly.LoadFrom("AIExample.dll").GetType("LensflareAIContest.AIHoldPosition")),
            (AI)Activator.CreateInstance(Assembly.LoadFrom("AIExample.dll").GetType("LensflareAIContest.AIExample")),
        };

        protected Color[] playerColors = new Color[] {
            new Color(1.0f, 0.2f, 0.1f),
            new Color(0.0f, 0.3f, 1.0f),
        };
        public Color[] PlayerColors { get { return playerColors; } protected set { playerColors = value; } }

        public enum State {
            WaitingForStart,
            Running,
            Paused,
            PresentingWinner,
        }

        public Rules Rules { get; protected set; }

        public State CurrentState { get; protected set; }

        public SpriteFont DefaultFont { get; protected set; }

        Texture2D panelTexture;
        Texture2D starTexture;

        float cameraZoomSpeed = 0.0f;
        Vector2 cameraDeltaScrollStartPos = Vector2.Zero;

        public SpriteBatch SpriteBatch          { get; protected set; }
        public TextureBuilder TextureBuilder    { get; protected set; }
        public Engine Engine                    { get; protected set; }
        public World World                      { get; protected set; }
        public KeyValueManager KeyValueManager  { get; protected set; }
        public SmoothCamera2 Camera             { get; protected set; }
        public Random Random                    { get; protected set; }

        public LayerManager LayerManager { get; protected set; }
        public LayerManager LayerManagerHud { get; protected set; }

        protected Info Info { get; set; }

        public List<Spaceship>[] Teams { get; protected set; }
        const int shipCountAtStart = 10;
        public int ShipCountAtStart { get { return shipCountAtStart; } }

        public Heart[] Hearts { get; protected set; }

        //int winnerIndex = -1;
        public int WinnerIndex { get; set; }

        public LinkedList<Event> Events { get; protected set; }

        public LensflareAIContestGame() {
            ProposeRules();

            Teams = new List<Spaceship>[Rules.World.TeamCount];
            Hearts = new Heart[Rules.World.TeamCount];

            Content.RootDirectory = "Content";

            Camera = new SmoothCamera2();
            Random = new Random();
            LayerManager = new LayerManager(EnumExtension.GetLength<MainLayer>());
            LayerManagerHud = new LayerManager(EnumExtension.GetLength<HudLayer>());

            Info = new Info(this);

            Engine = new Engine(this);

            Events = new LinkedList<Event>();

            WinnerIndex = -1;

            CurrentState = State.Running;
        }

        private void ProposeRules() {
            SpaceshipRules spaceship = new SpaceshipRules();
            spaceship.AccelerationMax = 1300.0f;
            spaceship.HealthMax = 10.0f;
            spaceship.EnergyMax = 1.0f;
            spaceship.EnergyNeededForShot = 0.3f;
            spaceship.EnergyRegenerationRate = 0.15f;
            spaceship.Radius = 13.0f;
            spaceship.CountPerTeam = 10;

            HeartRules heart = new HeartRules();
            heart.OffsetFromWorldCenter = 1000;
            heart.HealthMax = spaceship.CountPerTeam * spaceship.HealthMax * 2.0f;
            heart.Radius = 34.0f;

            ProjectileRules projectile = new ProjectileRules();
            projectile.DamageCausedOnHit = 4.0f;
            projectile.LifeSpan = 10.0f;
            projectile.Radius = 5.0f;
            projectile.VelocityInitial = 500.0f;

            WorldRules world = new WorldRules();
            world.Extent = new Vector2(4000, 2000);
            world.PhysicsSpaceScale = 0.01f;
            world.TeamCount = 2;
            world.UnitSpawnDistanceFromHeart = (heart.Radius + spaceship.Radius) * 1.3f;

            Rules = new Rules();
            Rules.Heart = heart;
            Rules.Projectile = projectile;
            Rules.Spaceship = spaceship;
            Rules.World = world;

            Debug.Assert(players.Length == world.TeamCount);
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize() {
            Engine.Initialize();
            DisplayMode displayMode = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode;
            Engine.GraphicsDeviceManager.ApplyResolution(displayMode.Width-100, displayMode.Height-100, false);

            TextureBuilder = new TextureBuilder(this.GraphicsDevice);

            Window.Title = "Lensflare AI Contest";

            //this.TargetElapsedTime = new TimeSpan(0, 0, 0, 0, 1000/60);
            this.IsFixedTimeStep = true;

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent() {
            SpriteBatch = new SpriteBatch(GraphicsDevice);

            Engine.Load();

            DefaultFont = Content.Load<SpriteFont>("defaultFont");

            float panelColorComponentTop = 0.2f;
            float panelColorComponentBottom = 0.1f;
            Color panelTopColor = new Color(panelColorComponentTop, panelColorComponentTop * 0.6f, panelColorComponentTop);
            Color panelBottomColor = new Color(panelColorComponentBottom, panelColorComponentBottom * 0.6f, panelColorComponentBottom);

            panelTexture = TextureBuilder.VerticalGradient(1, 42, panelTopColor, panelBottomColor);
            starTexture = TextureBuilder.EuclideanDistance(8, 8, Color.White);

            { // ring textures
                HillFunctionParameters fp = new HillFunctionParameters();
                fp.bottomInside = 0.3f;
                fp.bottomOutside = 0.0f;
                fp.top = 1.0f;
                fp.increaseLength = 0.05f;
                fp.decreaseLength = 0.05f;
                fp.topLength = 0.2f;

                Spaceship.Texture = TextureBuilder.Ring(256, Color.White, fp);

                fp.increaseLength *= 0.5f;
                fp.topLength *= 0.5f;
                fp.decreaseLength *= 0.5f;

                Heart.Texture = TextureBuilder.Ring(256, Color.White, fp);

                fp.bottomInside = 1.0f;
                fp.increaseLength = 0;
                fp.topLength = 0.9f;
                fp.decreaseLength = 0.1f;

                //Projectile.Texture = TextureBuilder.Ring(64, Color.White, fp);
                Projectile.Texture = TextureBuilder.Sphere(64, Color.White);
            } 

			KeyValueManager = new KeyValueManager( new MessageManager( SpriteBatch, DefaultFont, new Vector2( 8, 8 ), Color.Yellow, 2 ) );

            /*Keys zoomKey = Keys.Z;
            KeyValueManager.SetValueForKey(zoomKey, 1.0f);
            KeyValueManager.SetValueStepForKey(zoomKey, 0.05f);*/

            Keys frustumKey = Keys.F;
            KeyValueManager.SetValueForKey(frustumKey, 1.0f);
            KeyValueManager.SetValueStepForKey(frustumKey, 0.05f);

            World = new World(Vector2.Zero);
            FarseerPhysics.Settings.UseFPECollisionCategories = true;

            Camera.PositionScreen = Engine.Viewport.GetCenter();
            Camera.ViewSize = Engine.Viewport.GetSize() * KeyValueManager.ValueForKey(Keys.F);
            Camera.Zoom = 0.6f;

            Entity.Add(new WorldFrameEntity(this, Vector2.Zero, Rules.World.Extent));

            Hearts[0] = new Heart(this, new Vector2(-Rules.Heart.OffsetFromWorldCenter, 0), 0);
            Hearts[1] = new Heart(this, new Vector2(Rules.Heart.OffsetFromWorldCenter, 0), 1);
            Entity.Add(Hearts[0]);
            Entity.Add(Hearts[1]);

            float distFromHeart = (Rules.Heart.Radius + Rules.Spaceship.Radius) * 1.3f;
            for (int teamIndex = 0; teamIndex < Rules.World.TeamCount; ++teamIndex) {
                Teams[teamIndex] = new List<Spaceship>();
                for (int i = 0; i < shipCountAtStart; ++i) {
                    //Vector2 unitPosition = new Vector2(this.Random.Next(-100, 100), this.Random.Next(-100, 100));
                    double angle = Math.PI * 2 / shipCountAtStart * i;
                    Vector2 unitPosition = new Vector2((float)Math.Cos(angle) * distFromHeart, (float)Math.Sin(angle) * distFromHeart) + Hearts[teamIndex].Position;
                    Spaceship spaceship = new Spaceship(this, unitPosition, teamIndex);
                    Teams[teamIndex].Add(spaceship);
                    Entity.Add(spaceship);
                }
            }

            for (int i = 0; i < players.Length; ++i) {
                players[i].Info = Info;
                players[i].TeamIndex = i;
            }

            for (int i = 0; i < 750; ++i) {
                Star star = new Star(this, starTexture);
                Entity.Add(star);
            }
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent() {
            //Unload any non ContentManager content here
        }

        protected void ProcessInput(GameTime gameTime) {
            float elapsedSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds;

            Vector2 mouseWorldPosition = Camera.Unproject(Input.MousePosition);

            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
                Keyboard.GetState().IsKeyDown(Keys.Escape)) {
                this.Exit();
            }

            Keys fullscreenKey = Keys.F7;
            DisplayMode displayMode = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode;
            if (Input.KeyboardPressed(fullscreenKey)) {
                IntVector2 viewSize;
                bool switchToFullscreen = !Engine.GraphicsDeviceManager.IsFullScreen;
                if (switchToFullscreen) {
                    viewSize = new IntVector2(displayMode.Width, displayMode.Height);
                } else {
                    viewSize = new IntVector2(1024, 768);
                }
                Engine.GraphicsDeviceManager.ApplyResolution(viewSize.X, viewSize.Y, switchToFullscreen);
                Camera.PositionScreen = viewSize * 0.5f;
            }

            if (Input.KeyboardPressed(Keys.Enter) || Input.KeyboardPressed(Keys.Space)) {
                if (CurrentState == State.WaitingForStart) {
                    CurrentState = State.Running;
                } else if (CurrentState == State.Running) {
                    CurrentState = State.Paused;
                } else if (CurrentState == State.Paused) {
                    CurrentState = State.Running;
                }
            }

			bool toggleHealthBar = Input.KeyboardPressed( Keys.H );
			bool toggleEnergyBar = Input.KeyboardPressed( Keys.E );
			bool toggleSpaceShipId = Input.KeyboardPressed( Keys.I );

			if ( toggleHealthBar || toggleEnergyBar || toggleSpaceShipId )
			{
				foreach( GameEntity entity in GameEntity.All )
				{
					if( entity is Heart)
					{
						Heart heart = (Heart)entity;
						if( toggleHealthBar )
							heart.ShowHealthBar = !heart.ShowHealthBar;
					}
					else if( entity is Spaceship)
					{
						Spaceship spaceship = (Spaceship)entity;
						if( toggleHealthBar )
							spaceship.ShowHealthBar = !spaceship.ShowHealthBar;
						if( toggleEnergyBar )
							spaceship.ShowEnergyBar = !spaceship.ShowEnergyBar;
						if ( toggleSpaceShipId )
							spaceship.ShowId = !spaceship.ShowId;
					}
				}
			}

            Keys frustumKey = Keys.F;
            Camera.ViewSize = Engine.Viewport.GetSize() * KeyValueManager.ValueForKey(frustumKey);

            bool pressingShiftKey = Keyboard.GetState().IsKeyDown(Keys.LeftShift) || Keyboard.GetState().IsKeyDown(Keys.RightShift);
            float movementBoost = 1.0f;
            if (pressingShiftKey) {
                movementBoost = 10.0f;
            }

            float cameraMovementSpeedKeys = 3000.0f * movementBoost * elapsedSeconds;
            float cameraMovementSpeedMouse = 100.0f * movementBoost * elapsedSeconds;

            Vector2 arrowKeysVector = Vector2.Zero;

            if (Keyboard.GetState().IsKeyDown(Keys.Left)) {
                arrowKeysVector.X -= 1.0f;
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Right)) {
                arrowKeysVector.X += 1.0f;
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Up)) {
                arrowKeysVector.Y -= 1.0f;
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Down)) {
                arrowKeysVector.Y += 1.0f;
            }

            Camera.Velocity += arrowKeysVector * cameraMovementSpeedKeys / Camera.Zoom;

            if (Input.MousePressing(Input.MouseButton.LeftButton)) {
                Camera.PositionWorld -= Input.MouseDelta / Camera.Zoom;
            }

            if (Input.MousePressed(Input.MouseButton.RightButton)) {
                cameraDeltaScrollStartPos = Input.MousePosition;
            }

            if (Input.MousePressing(Input.MouseButton.RightButton)) {
                Camera.Velocity += (Input.MousePosition - cameraDeltaScrollStartPos) / Camera.Zoom * 0.3f;
            }

            cameraZoomSpeed += Input.MouseWheelDelta * 0.1f * movementBoost;
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime) {
            bool pauseWhenOutOfFocus = false;

            if (IsActive || !pauseWhenOutOfFocus) {
                float elapsedSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds;
                float totalSeconds = (float)gameTime.TotalGameTime.TotalSeconds;

                ProcessInput(gameTime);

                KeyValueManager.Update(gameTime);

                //Camera.Zoom = KeyValueManager.ValueForKey(Keys.Z);
                float prevZoom = Camera.Zoom;
                Camera.Zoom *= (float)Math.Pow(1.001, cameraZoomSpeed);
                cameraZoomSpeed *= 0.9f;
                //TODO: move to the camera class

                /*
                float deltaZoom = Camera.Zoom - prevZoom;
                Vector2 mousePos = Input.MousePosition;
                Vector2 mouseOffsetFromCenter = (mousePos - Engine.Viewport.GetCenter()) / deltaZoom;
                Camera.PositionWorld += mouseOffsetFromCenter;
                */

                //TODO: zoom snap to 1.0
                //TODO: zoom bounds

                bool updateEntities = CurrentState != State.WaitingForStart && CurrentState != State.Paused;

                if (updateEntities) {
                    for (int teamIndex = 0; teamIndex < Rules.World.TeamCount; ++teamIndex) {
                        players[teamIndex].TimeElapsed(elapsedSeconds, totalSeconds);
                        int shipCount = Teams[teamIndex].Count;
                        for (int i = 0; i < shipCount; ++i) {
                            Spaceship ship = Teams[teamIndex].ElementAt(i);
                            ship.Thrust = players[teamIndex].ThrustForUnit(i);
                            Vector2? shot = players[teamIndex].PreparedShotForUnit(i);
                            if (shot.HasValue) {
                                ship.Shoot(shot.Value);
                            }
                        }
                    }
                }

                Events.Clear();

                //Camera world border bounce
                if(!Input.MousePressing(Input.MouseButton.LeftButton)) {
                    Vector2 worldExtentHalf = Rules.World.Extent * 0.5f;
                    Vector2 camVel = Camera.Velocity;
                    if (Camera.PositionWorld.X > worldExtentHalf.X) {
                        camVel.X -= Camera.PositionWorld.X - worldExtentHalf.X;
                    } else if (Camera.PositionWorld.X < -worldExtentHalf.X) {
                        camVel.X += -worldExtentHalf.X - Camera.PositionWorld.X;
                    }
                    if (Camera.PositionWorld.Y > worldExtentHalf.Y) {
                        camVel.Y -= Camera.PositionWorld.Y - worldExtentHalf.Y;
                    } else if (Camera.PositionWorld.Y < -worldExtentHalf.Y) {
                        camVel.Y += -worldExtentHalf.Y - Camera.PositionWorld.Y;
                    }
                    Camera.Velocity = camVel;
                }

                Camera.Update(gameTime);

                if (updateEntities) {
                    World.Step(elapsedSeconds);
                }
                Engine.Update2D(gameTime, updateEntities, true);
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime) {
            float bgColorComponent = 0.06f;
            GraphicsDevice.Clear(new Color(bgColorComponent, 0.0f, bgColorComponent));

            Engine.Draw2D(gameTime);

            SpriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);

            Entity.DrawAll();

            //Test
            //SpriteBatch.Draw(worldBackgroundTexture, Engine.Viewport.GetCenter() - worldBackgroundTexture.GetCenter() - Camera.PositionWorld * 0.002f, null, Color.White, 0, Vector2.Zero, 1, SpriteEffects.None, LayerManager.Depth((int)MainLayer.Background));

            //camera:
            Primitive2.DrawRect(SpriteBatch, new IntVector2(Camera.PositionScreen) - Camera.ViewSize * 0.5f - Vector2.One * 1.001f, Camera.ViewSize + Vector2.One * 2 * 1.001f, Color.White, false, LayerManager.Depth((int)MainLayer.Hud)); //camera frame

            //hud:
            Vector2 mousePos = Input.MousePosition;
            
            String fpsString = "FPS: " + Engine.Fps;
			Vector2 fpsStringSize = DefaultFont.MeasureString( fpsString );
			SpriteBatch.DrawString( DefaultFont, fpsString, new Vector2( Engine.Viewport.Width - fpsStringSize.X - 8, 0 ), Color.Cyan ); //TODO: layerDepth

            int panelHeight = panelTexture.Height;
            float panelLayerDepth = LayerManager.Depth((int)MainLayer.Hud, LayerManagerHud.Depth((int)HudLayer.Panel));
            SpriteBatch.Draw(panelTexture, new Rectangle(0, Engine.Viewport.Height - panelHeight, Engine.Viewport.Width, panelHeight), null, Color.White, 0.0f, Vector2.Zero, SpriteEffects.None, panelLayerDepth);

            float textLayerDepth = LayerManager.Depth((int)MainLayer.Hud, LayerManagerHud.Depth((int)HudLayer.Text));
            float textShadowLayerDepth = LayerManager.Depth((int)MainLayer.Hud, LayerManagerHud.Depth((int)HudLayer.TextShadow));

            const float textScratchDepth = 0.5f;
            const int marginX = 48;
            const int marginY = 4;

            if (players[0] != null) {
                String versionString = "" + players[0].GetName();
				Vector2 versionStringSize = DefaultFont.MeasureString( versionString );
                Vector2 pos = new Vector2(marginX, Engine.Viewport.Height - versionStringSize.Y - marginY);
                Color color = PlayerColors[0];
                SpriteBatch.DrawString( DefaultFont, versionString, pos + new Vector2(0, 1), Color.Gray * textScratchDepth, 0, Vector2.Zero, 1, SpriteEffects.None, textShadowLayerDepth);
				SpriteBatch.DrawString( DefaultFont, versionString, pos + new Vector2( 0, -1 ), Color.Black * textScratchDepth, 0, Vector2.Zero, 1, SpriteEffects.None, textShadowLayerDepth );
				SpriteBatch.DrawString( DefaultFont, versionString, pos, color, 0, Vector2.Zero, 1, SpriteEffects.None, textLayerDepth );
            }

            if (players[1] != null) {
                String versionString = "" + players[1].GetName();
				Vector2 versionStringSize = DefaultFont.MeasureString( versionString );
                Vector2 pos = new Vector2(Engine.Viewport.Width - versionStringSize.X - marginX, Engine.Viewport.Height - versionStringSize.Y - marginY);
                Color color = PlayerColors[1];
				SpriteBatch.DrawString( DefaultFont, versionString, pos + new Vector2( 0, 1 ), Color.Gray * textScratchDepth, 0, Vector2.Zero, 1, SpriteEffects.None, textShadowLayerDepth );
				SpriteBatch.DrawString( DefaultFont, versionString, pos + new Vector2( 0, -1 ), Color.Black * textScratchDepth, 0, Vector2.Zero, 1, SpriteEffects.None, textShadowLayerDepth );
				SpriteBatch.DrawString( DefaultFont, versionString, pos, color, 0, Vector2.Zero, 1, SpriteEffects.None, textLayerDepth );
            }

            if (WinnerIndex >= 0) {
                string winMessage = "Winner: " + players[WinnerIndex].GetName();
                Vector2 winMessageSize = DefaultFont.MeasureString(winMessage);
                Vector2 winMessagePosition = new Vector2(Engine.Viewport.GetSize().X * 0.5f - winMessageSize.X * 0.5f, Engine.Viewport.GetSize().Y - winMessageSize.Y - panelHeight - 10);
                SpriteBatch.DrawString(DefaultFont, winMessage, winMessagePosition, PlayerColors[WinnerIndex]);
            }

            KeyValueManager.Draw();

            float mouseLayerDepth = LayerManager.Depth((int)MainLayer.MouseCursor);
            Primitive2.DrawCircle(SpriteBatch, mousePos, 4, Color.DarkCyan, false, mouseLayerDepth);
            Primitive2.DrawCircle(SpriteBatch, mousePos, 5, Color.Cyan, false, mouseLayerDepth);
            Primitive2.DrawCircle(SpriteBatch, mousePos, 6, Color.DarkCyan, false, mouseLayerDepth);

            SpriteBatch.End();

            base.Draw(gameTime);
        }
    }
}
